package com.example.elasticScrollView;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;

import java.lang.reflect.Field;

/**
 * Created by Maijx on 2015/5/11.
 */
public class ObservableScrollView extends ScrollView {

    public static final String TAG = ObservableScrollView.class.getSimpleName();

    private View headerView;

    private ScrollViewListener scrollViewListener;
    private OverScrollListener overScrollListener;

    /** 第一次落点位置 */
    private float initY;

    /** 记录Header初始高度 */
    private int headerHeight;

    private boolean isTopOverScroll;

    /** 第一次检测到的触电id */
    private int mActivePointerId;

    /** 记录上一次触点Y值 */
    private float lastY;

    /** header是否缩放中 */
    private boolean isScaling;

    /** header最大拉伸值 */
    private int maxHeaderHeight;

    private static final int MAX_Y_OVERSCROLL_DISTANCE = 50;

    private int mMaxYOverscrollDistance;

    private GestureDetector.OnGestureListener listener = new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

            //            Log.i(TAG, "onFling() e1=" + e1.getY() + ",e2=" + e2.getY() + ",e2.action=" + e2.getAction() + ",velocityY=" + velocityY);

            if (e1.getY() < e2.getY() && velocityY > 10000 && elasticOa == null) {
                //                startAnimation();
            }

            return super.onFling(e1, e2, velocityX, velocityY);
        }
    };

    private GestureDetector detector = new GestureDetector(getContext(), listener);
    private ObjectAnimator elasticOa;

    public ObservableScrollView(Context context) {
        super(context);

        initBounceListView();
    }

    public ObservableScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        initBounceListView();
    }

    private void initBounceListView() {
        //get the density of the screen and do some maths with it on the max overscroll distance
        //variable so that you get similar behaviors no matter what the screen size

        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final float density = metrics.density;

        mMaxYOverscrollDistance = (int) (density * MAX_Y_OVERSCROLL_DISTANCE);

        try {
            Class<?> c = Class.forName(ScrollView.class.getName());
            Field egtField = c.getDeclaredField("mEdgeGlowTop");
            Field egbBottom = c.getDeclaredField("mEdgeGlowBottom");
            egtField.setAccessible(true);
            egbBottom.setAccessible(true);
            Object egtObject = egtField.get(this); // this 指的是ListiVew实例
            Object egbObject = egbBottom.get(this);

            // egtObject.getClass() 实际上是一个 EdgeEffect 其中有两个重要属性 mGlow mEdge
            // 并且这两个属性都是Drawable类型
            Class<?> cc = Class.forName(egtObject.getClass().getName());
            Field mGlow = cc.getDeclaredField("mGlow");
            mGlow.setAccessible(true);
            mGlow.set(egtObject, new ColorDrawable(Color.TRANSPARENT));
            mGlow.set(egbObject, new ColorDrawable(Color.TRANSPARENT));

            Field mEdge = cc.getDeclaredField("mEdge");
            mEdge.setAccessible(true);
            mEdge.set(egtObject, new ColorDrawable(Color.TRANSPARENT));
            mEdge.set(egbObject, new ColorDrawable(Color.TRANSPARENT));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        final int oldY = getScrollY();
        final int newY = scrollY;

        //        if (clampedY) {
        //            scrollY = (oldY + newY) / 2;
        //            scrollY = oldY;
        //
        //            Log.i(TAG, "onOverScrolled() clampedY==true, scrollY=" + scrollY);
        //
        //            super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        //        } else
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);

        if (clampedY && scrollY <= 0)
            isTopOverScroll = true;
        else
            isTopOverScroll = false;

        //        Log.i(TAG, "onOverScrolled()  isTopOverScroll=" + isTopOverScroll + ",scrollY=" + scrollY);
        Log.i(TAG, "onOverScrolled() scrollY=" + scrollY);

        if (overScrollListener != null) {
            overScrollListener.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();

        Log.i(TAG, "computeScroll()");
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
        //        Log.i(TAG, "deltaY=" + deltaY + ",scrollY=" + scrollY + ",scrollRangeY=" + scrollRangeY + ",height=" + getHeight() + ",isTouchEvent=" + isTouchEvent);

        final int overScrollMode = getOverScrollMode();
        final boolean canScrollVertical = computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS || (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        if (clampedY && newScrollY > 0) {
            return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, mMaxYOverscrollDistance, isTouchEvent);

        } else if (clampedY && newScrollY <= 0) {

            return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
        }

        return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
    }

    @Override
    protected void onScrollChanged(int x, int y, int oldx, int oldy) {
        super.onScrollChanged(x, y, oldx, oldy);

        if (scrollViewListener != null) {
            scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
        }

        //        Log.i(TAG, "onScrollChanged() y=" + y + ",oldY=" + oldy);

        // 当overScroll后，再上滑动，ScrollView的overScroll标记消失，但header还在缩放过程
        if (isScaling) {
            scrollBy(0, -y);// 回到原来位置
        }

        //        if (!isScaling && y < 0) {
        //            scrollBy(0, -y);
        //        }
    }

    public boolean isTopOverScroll() {
        return isTopOverScroll;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (headerHeight == 0 && headerView != null) {
            headerHeight = headerView.getHeight();
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {

        detector.onTouchEvent(ev);

        switch (ev.getAction()) {
            case MotionEvent.ACTION_CANCEL:

                break;

            case MotionEvent.ACTION_DOWN:

                this.mActivePointerId = ev.getPointerId(0); // 第一个触点的id值

                break;
            case MotionEvent.ACTION_UP: // 手势结束后，重置数值
                this.initY = 0;
                this.mActivePointerId = -1;
                this.lastY = 0;
                this.isScaling = false;

                // Header反弹回正常高度
                if (this.headerView != null) {

                    // ObjectAnimator只为了计算数值而已，在UpdateListener里面才把数值赋值给headerView
                    ObjectAnimator oa = ObjectAnimator.ofInt(new Object(), "h", this.headerView.getHeight(), this.headerHeight);
                    oa.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                        @Override
                        public void onAnimationUpdate(ValueAnimator animation) {

                            int value = (int) animation.getAnimatedValue();

                            // Log.i(TAG, "onAnimationUpdate value=" + value);

                            ViewGroup.LayoutParams params = headerView.getLayoutParams();
                            params.height = value;

                            headerView.setLayoutParams(params);
                        }
                    });
                    oa.setDuration(200);
                    oa.start();
                }

                break;

            case MotionEvent.ACTION_MOVE:

                // 通过触点的id值得到触点的索引值
                int pointerIndex = ev.findPointerIndex(this.mActivePointerId);

                if ((isTopOverScroll() || isScaling) && pointerIndex > -1) {
                    this.isScaling = true;

                    float currentY = ev.getY(pointerIndex);

                    if (this.initY == 0) {
                        this.initY = currentY;

                    } else {

                        lastY = currentY;

                        double M = maxHeaderHeight - headerHeight;
                        double n = M * 8;
                        double mid = M;

                        double C = (-Math.pow(M, 2) / (8d * n)) + M / 4;
                        double p = 16 * (M - C);

                        float dy = currentY - this.initY;// 手势拉伸距离

                        double maxDy = mid + p;

                        if (dy < maxDy) {

                            /** 比原来增加的高度 */
                            double dHeight = (-Math.pow(dy, 2d) / (8d * n)) + dy / 4;// 二次函数，dy=n有极值

                            if (dy < M) {
                                //                                Log.i(TAG, "dy < M,dy=" + dy + ",addHeight=" + addHeight + " , M=" + M);

                                double x = dy;

                                dHeight = (-Math.pow(x, 2) / (8d * n)) + x / 4;

                            } else { // 第二段初始斜率g'(M) < f'(M)，g(M)也比f(M)更平坦

                                //                                Log.i(TAG, "else,dy=" + dy + ",addHeight=" + addHeight + " , maxDy=" + maxDy);

                                double x = dy - mid;

                                dHeight = -Math.pow(x, 2) / (16d * p) + x / 8d + C;
                            }

                            // 计算后高度
                            double newHeight = headerHeight + dHeight;

                            // 缩放header
                            if (headerView != null && newHeight > headerHeight) {
                                ViewGroup.LayoutParams params = this.headerView.getLayoutParams();
                                params.height = (int) (headerHeight + dHeight);

                                this.headerView.setLayoutParams(params);
                            } else {
                                isScaling = false;
                            }
                        }
                    }
                }

                break;

            case MotionEvent.ACTION_POINTER_1_UP: // 当存在2个触点以上，第一个触点松开

                resetPointer(ev, 1);// 第二个触点为主

                break;

            case MotionEvent.ACTION_POINTER_2_UP:

                resetPointer(ev, 0);// 第一个触点为主

                break;

            default:
                break;
        }

        return super.onTouchEvent(ev);
    }

    /**
     * 重新计算初始落点 {@linkplain #initY}
     *
     * @param ev
     * @param pointerIndex
     */
    private void resetPointer(MotionEvent ev, int pointerIndex) {

        this.mActivePointerId = ev.getPointerId(pointerIndex);

        float currentY = ev.getY(ev.findPointerIndex(this.mActivePointerId));

        // initY + (触点1位置-触点2位置)
        this.initY = this.initY + (currentY - this.lastY);
    }

    public void setHeader(View headerView) {
        this.headerView = headerView;
    }

    /**
     * 设置header最大高度
     *
     * @param maxHeight
     */
    public void setHeaderMaxHeight(int maxHeight) {
        this.maxHeaderHeight = maxHeight;
    }

    public void setScrollViewListener(ScrollViewListener listener) {
        this.scrollViewListener = listener;
    }

    public void setOverScrollListener(OverScrollListener overScrollListener) {
        this.overScrollListener = overScrollListener;
    }

    public interface ScrollViewListener {
        void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy);
    }

    public interface OverScrollListener {
        void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY);
    }
}


